﻿/* 
 Word Custom XML data services
 (C) 2012 Jean-Alain Baeyens
 Under Microsoft Reciprocal License (Ms-RL) 
 http://wordxmldatasrv.codeplex.com
*/

using System.IO;
using System.Text;
using System.Xml;
using DocumentFormat.OpenXml.Packaging;

namespace WordUtilities
{
    public class CustomXmlUtilities
    {
        #region Public methode

        /// <summary>
        /// Add or update custom XML part with nidhi namespace and fill it with the XML string
        /// </summary>
        /// <param name="docStream">Stream on Word document</param>
        /// <param name="customXML">XML to be embended into Word document</param>
        /// <returns></returns>
        public Stream MergeCustomXML(Stream docStream, string customXML)
        {
            WordprocessingDocument docx = WordprocessingDocument.Open(docStream, true);

            XmlDocument customXMLData = createXmlDocument(customXML);
            string uri = customXMLData.DocumentElement.NamespaceURI;

            CustomXmlPart customXMLPart = getCustomXmlPart(uri, docx);

            if (customXMLPart == null)
            {
                customXMLPart = docx.MainDocumentPart.AddCustomXmlPart(CustomXmlPartType.CustomXml);
            }

            setCusomXmlPartContent(customXMLData, customXMLPart);
            docx.Close();

            return docStream;
        }

        /// <summary>
        /// Retrieve the content of the custom XML part with name space nidhi and return it as a string
        /// </summary>
        /// <param name="docStream">stream on the Word document</param>
        /// <returns>XML data embended into the Word document</returns>
        public string GetCustomXML(Stream docStream, string uri)
        {
            string xmlContent = string.Empty;

            WordprocessingDocument docx = WordprocessingDocument.Open(docStream, false);
            CustomXmlPart customXMLPart = getCustomXmlPart(uri, docx);
            if (customXMLPart != null)
            {
                xmlContent = getCusomXmlPartContent(customXMLPart);
            }
            docx.Close();

            return xmlContent;
        }

        #endregion

        #region private methodes

        /// <summary>
        /// Search the custom XML part corresponding to the defined schema. The first one is returned.
        /// </summary>
        /// <param name="uri">Url schema of the searched custom Xml part</param>
        /// <param name="docx">Word document used</param>
        /// <returns>Custom Xml part</returns>
        private CustomXmlPart getCustomXmlPart(string uri, WordprocessingDocument docx)
        {
            // get the XML part that needs to be updated
            CustomXmlPart customXMLPart = null;

            foreach (CustomXmlPart part in docx.MainDocumentPart.CustomXmlParts)
            {
                string content = getCusomXmlPartContent(part);
                XmlDocument xmlContent = createXmlDocument(content);
                string contentUri = xmlContent.DocumentElement.NamespaceURI;
                if (uri == contentUri)
                {
                    customXMLPart = part;
                    break;
                }
            }
            return customXMLPart;
        }

        /// <summary>
        /// Create XML document based on a XML string
        /// </summary>
        /// <param name="customXML"></param>
        /// <returns></returns>
        private XmlDocument createXmlDocument(string customXML)
        {
            // load custom XML
            XmlDocument customXMLData = new XmlDocument();
            customXMLData.LoadXml(customXML);

            return customXMLData;
        }

        /// <summary>
        /// Set custom XML part with the content of the XML file
        /// </summary>
        /// <param name="customXML">XML data</param>
        /// <param name="customXMLPart">Custom XML part to be updated</param>
        private void setCusomXmlPartContent(XmlDocument customXML, CustomXmlPart customXMLPart)
        {
            // save custom XML in part
            using (Stream xmlStream = customXMLPart.GetStream(FileMode.Create))
            {
                customXML.Save(xmlStream);
            }
        }

        /// <summary>
        /// get the content of the given custom XML part and return it as a string
        /// </summary>
        /// <param name="customXMLPart">Custom XML part </param>
        /// <returns>XML string contains in custom XMl part</returns>
        private string getCusomXmlPartContent(CustomXmlPart customXMLPart)
        {
            using (Stream xmlStream = customXMLPart.GetStream(FileMode.Open))
            {
                byte[] buffer = new byte[xmlStream.Length];
                xmlStream.Read(buffer,0,buffer.Length);
                string xmlContent = string.Empty;
                xmlContent = convertBytesToString(buffer);
                // Depending of encoding BIM can be present (3 extra bytes at the beginning).
                // and must be removed
                while (!xmlContent.StartsWith("<"))
                {
                    xmlContent = xmlContent.Substring(1);
                }
                return xmlContent;
            }
        }

        /// <summary>
        /// Convert byte array to a string encoded in UTF8
        /// </summary>
        /// <param name="array">byte array to be converted</param>
        /// <returns>utf8 string</returns>
        private string convertBytesToString(byte[] array)
        {
            UTF8Encoding encoder = new UTF8Encoding();
            return encoder.GetString(array);
        }

        #endregion
    }
}
